package com.qumitech.visualnovel.component.util;

import com.alibaba.fastjson.JSONObject;
import com.qumitech.visualnovel.component.util.http.HttpDeleteWithEntity;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.*;

@Component
public class HttpInvokeUtil
{

    private static final Logger log = LoggerFactory.getLogger(HttpInvokeUtil.class);

    private static Boolean grayLevel = Boolean.parseBoolean(GetPropertiesUtils.getProperty("common", "gray_level"));

    private static String grayUid = GetPropertiesUtils.getProperty("common", "gray_uid");


    public static String httpDelete(String url, Map<String, String> headerMap) {
        headerMap = enableGrayLevel(headerMap);
        ResponseHandler<String> responseHandler = response -> {
            int status = response.getStatusLine().getStatusCode();
            if (status >= 200 && status < 300) {
                HttpEntity entity = response.getEntity();
                return entity != null ? EntityUtils.toString(entity) : null;
            } else {
                throw new ClientProtocolException("Unexpected response status: " + status);
            }
        };

        CloseableHttpClient client = HttpClients.createDefault();
        try {
            HttpDelete httpDelete = new HttpDelete(url);
            for (Map.Entry<String, String> entry: headerMap.entrySet()){
                httpDelete.setHeader(entry.getKey(), entry.getValue());
            }
            return client.execute(httpDelete, responseHandler);
        } catch (Throwable e) {
            log.error("http simple delete is error", e);
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                log.error("close simple delete http client is error", e);
            }
        }
        return "";
    }

    public static String httpDeleteWithEntity(String url, String entity, Map<String, String> headerMap, String encode) {
        headerMap = enableGrayLevel(headerMap);
        CloseableHttpClient client = HttpClients.createDefault();
        HttpResponse response = null;
        try {
            HttpDeleteWithEntity httpDelete = new HttpDeleteWithEntity(URI.create(url));
            for (Map.Entry<String, String> entry: headerMap.entrySet()){
                httpDelete.setHeader(entry.getKey(), entry.getValue());
            }
            httpDelete.setEntity(new StringEntity(entity, ContentType.APPLICATION_JSON));
            response = client.execute(httpDelete);
            return EntityUtils.toString(response.getEntity(), encode);
        } catch (Throwable e) {
            log.error("http simple delete is error", e);
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                log.error("close simple delete http client is error", e);
            }
        }

        return "";
    }

    /**
     * http Post 请求
     * 
     * @param url
     *        请求路径
     * @param paramMap
     *        请求参数
     * @param encode
     *        请求参数的url编码，及返回数据的编码
     * @return
     */
    public static String httpPost(String url, Map<String, String> paramMap, String encode) {

        CloseableHttpClient client = HttpClients.createDefault();
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(getPostParam(paramMap, encode));
            return httpPost(httpPost, encode);
        } catch (Throwable e) {
            log.error("http simple post is error", e);
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                log.error("close simple post http client is error", e);
            }
        }

        return "";
    }

    public static String httpPostJson(String url, Map<String, String> paramMap, String encode) {
        return httpPostJson(url, JSONObject.toJSONString(paramMap), encode, null);
    }

    public static String httpPostJson(String url, JSONObject paramJson, String encode, Map<String, String> headerMap) {

        return httpPostJson(url, paramJson.toJSONString(), encode, headerMap);
    }

    public static String httpPutJson(String url, JSONObject paramJson, String encode, Map<String, String> headerMap) {

        return httpPutJson(url, paramJson.toJSONString(), encode, headerMap);
    }

    public static String httpPostJson(String url, String paramJsonStr, String encode, Map<String, String> headerMap) {
        log.info("url:{}, req:{}", url, paramJsonStr);
        headerMap = enableGrayLevel(headerMap);
        CloseableHttpClient client = HttpClients.createDefault();
        try {
            HttpPost httpPost = new HttpPost(url);
            for (Map.Entry<String, String> item : headerMap.entrySet()) {
                httpPost.addHeader(item.getKey(), item.getValue());
            }
            httpPost.setEntity(new StringEntity(paramJsonStr, ContentType.APPLICATION_JSON));
            return httpPost(httpPost, encode);
        } catch (Throwable e) {
            log.error("http simple post is error", e);
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                log.error("close simple post http client is error", e);
            }
        }

        return "";
    }

    public static String httpPutJson(String url, String paramJsonStr, String encode, Map<String, String> headerMap) {
        headerMap = enableGrayLevel(headerMap);
        CloseableHttpClient client = HttpClients.createDefault();
        try {
            HttpPut httpPut = new HttpPut(url);
            for (Map.Entry<String, String> item : headerMap.entrySet()) {
                httpPut.addHeader(item.getKey(), item.getValue());
            }
            httpPut.setEntity(new StringEntity(paramJsonStr, ContentType.APPLICATION_JSON));
            return httpPut(httpPut, encode);
        } catch (Throwable e) {
            log.error("http simple put is error", e);
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                log.error("close simple put http client is error", e);
            }
        }

        return "";
    }

    // post请求参数，请求参数使用UTF-8编码
    private static HttpEntity getPostParam(Map<String, String> paramMap, String encode) throws Exception
    {
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        if (paramMap != null && !paramMap.isEmpty()) {
            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }

        return new UrlEncodedFormEntity(nvps, Charset.forName(encode));
    }

    public static String httpPost(HttpPost post, String encode)
    {
        CloseableHttpClient httpClient = HttpClients.createDefault();

        HttpResponse response = null;
        try
        { // 链接超时，请求超时设置
            RequestConfig reqConf = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
            post.setConfig(reqConf);
            response = httpClient.execute(post);
            String resContent = EntityUtils.toString(response.getEntity(), encode);
            //log.info("[httpPost] response:{}", resContent);
            return resContent;
        } catch (Throwable e) {
            log.error("http post request error", e);
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                log.error("httpPost", e);
            }
        }
        return "";
    }

    public static String httpPut(HttpPut put, String encode)
    {
        CloseableHttpClient httpClient = HttpClients.createDefault();

        HttpResponse response = null;
        try
        { // 链接超时，请求超时设置
            RequestConfig reqConf = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
            put.setConfig(reqConf);
            response = httpClient.execute(put);
            String resContent = EntityUtils.toString(response.getEntity(), encode);
            //log.info("[httpPut] response:{}", resContent);
            return resContent;
        } catch (Throwable e) {
            log.error("http put request error", e);
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                log.error("httpPut", e);
            }
        }
        return "";
    }

    /**
     * http Post 请求包括上传文件,除了文件参数之外,还可以有其他参数
     * 
     * @param url
     * @param paramMap
     * @return
     */
    public static String httpPostFile(String url, Map<String, String> paramMap, String fileFormName, String filePath,
            String encode)
    {
        if (StringUtils.isEmpty(url)) {
            throw new RuntimeException("url参数错误");
        }

        CloseableHttpClient client = null;
        CloseableHttpResponse resp = null;
        try {
            // 请求参数构建
            HttpEntity entity = getPostParam(paramMap, fileFormName, filePath);
            // 链接超时，请求超时设置
            RequestConfig reqConf = RequestConfig.custom().setSocketTimeout(5 * 1000).setConnectTimeout(5 * 1000)
                    .build();
            client = HttpClientBuilder.create().build();

            HttpPost post = new HttpPost(url);
            post.setEntity(entity);// 设置参数
            post.setConfig(reqConf);// 设置配置
            resp = client.execute(post);

            return handlerResponse(resp, encode);
        } catch (Throwable e) {
            log.error("http post request error", e);
        } finally {
            try {
                if (client != null) {
                    client.close();
                }
                if (resp != null) {
                    resp.close();
                }
            } catch (IOException e) {
                log.error("close muitl file post http client is error", e);
            }
        }
        return "";
    }

    /**
     * htppGET请求，返回字符
     * 
     * @param url
     * @param paramMap
     * @return String
     * @throws Exception
     */
    public static String httpGetWithHeader(String url, Map<String, String> paramMap, Map<String, String> headerMap)
    {
        headerMap = enableGrayLevel(headerMap);
        log.info("[httpGetWithHeader] url:{}, paramMap:{}, headerMap:{}", url, paramMap, headerMap);
        if (StringUtils.isEmpty(url)) {
            throw new RuntimeException("url参数错误");
        }
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse resp = null;
        try {
            String encodedUrl = encodeGetUrl(url, paramMap);
            HttpGet httpGet = new HttpGet(encodedUrl);
            addHeader(httpGet, headerMap);
            RequestConfig reqConf = RequestConfig.custom().setSocketTimeout(30 * 1000).setConnectTimeout(30 * 1000)
                    .build();
            httpGet.setConfig(reqConf);
            // 发起请求 并返回请求的响应
            resp = httpClient.execute(httpGet);
            // 返回响应结果
            return handlerResponse(resp, "UTF-8");
        } catch (Throwable e) {
            throw new RuntimeException("url: " + url, e);
        } finally {
            try {
                httpClient.close();
                if (resp != null) {
                    resp.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static Map<String, String> getAuthHeader(String appKey, String appToken) {
        String auth = appKey + ":" + appToken;
        byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(Charset.forName("US-ASCII")));
        String authHeader = "Basic " + new String(encodedAuth);
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("Authorization", authHeader);
        return headerMap;
    }

    private static void addHeader(HttpGet httpGet, Map<String, String> headerMap) {
        if (headerMap == null)
            return;
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            httpGet.addHeader(entry.getKey(), entry.getValue());
        }
    }

    public static HttpEntity streamHttpGet(String url, Map<String, String> paramMap) {
        if (StringUtils.isEmpty(url)) {
            throw new RuntimeException("url参数错误");
        }

        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        HttpResponse resp = null;
        try {
            HttpGet httpGet = new HttpGet(encodeGetUrl(url, paramMap));
            // 发起请求 并返回请求的响应
            resp = httpClient.execute(httpGet);
            // 直接返回 Inputstream 会产生 Attempted read from closed stream.错误。流会关闭。
            return resp.getEntity();
        }
        catch (Throwable e) {
            log.error("http get request error", e);
        }

        return null;
    }

    /**
     * 生成满足Get请求方式的URI规范路径, 如果参数paramMap不为空, 则转换编码后和url进行拼接, 如下格式URL都可以自动处理:
     */
    private static String encodeGetUrl(String url, Map<String, String> paramMap) throws URISyntaxException
    {
        URIBuilder uriBuilder = new URIBuilder(url);
        if (MapUtils.isNotEmpty(paramMap)) {
            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                uriBuilder.addParameter(entry.getKey(), entry.getValue());
            }
        }
        return uriBuilder.toString();
    }

    // post请求参数，请求参数使用UTF-8编码
    private static HttpEntity getPostParam(Map<String, String> paramMap, String fileName, String filePath)
            throws Exception
    {
        MultipartEntityBuilder reqEntity = MultipartEntityBuilder.create();
        if (paramMap != null && !paramMap.isEmpty()) {
            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                String paramName = entry.getKey();
                String paramValue = entry.getValue();

                reqEntity.addTextBody(paramName, paramValue, ContentType.create("text/plain", Consts.UTF_8));
            }
        }

        if (!StringUtils.isEmpty(filePath)) {
            reqEntity.addBinaryBody(fileName, new File(filePath));
        }

        return reqEntity.build();
    }

    // 处理返回结果
    private static String handlerResponse(HttpResponse resp, String encode) throws Exception {
        StatusLine statusLine = resp.getStatusLine();
        if (statusLine.getStatusCode() >= 300) {
            throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
        }

        // 获取响应对象
        HttpEntity resEntity = resp.getEntity();
        if (resEntity == null) {
            throw new ClientProtocolException("response contains no content");
        }
        String resContent = EntityUtils.toString(resEntity, Charset.forName(encode));
        //log.info("[handlerResponse] response:{}", resContent);
        EntityUtils.consume(resEntity); // 销毁
        return resContent;
    }

    public static void main(String[] args) throws URISyntaxException {
        Map<String, String> params = new HashMap<>();
        params.put("key1", "values1");
        params.put("key2", "指尖");
        String url = encodeGetUrl("http://www.cc.com?name=zjcs&", params);
       //String rsp = httpGet(url, null,null);
        //System.out.println(rsp);
    }

    public static String httpGet(String url, Map<String, String> paramMap,Map<String, String> headerMap) {
        return httpGetWithHeader(url, paramMap, headerMap);
    }

    public static String httpGet(String url, Map<String, Object> paramMap) {
        Map<String, String> param = new HashMap<>();
        if (paramMap != null) {
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                param.put(entry.getKey(), entry.getValue().toString());
            }
        }
        return httpGetWithHeader(url, param, new HashMap<>());
    }

    public static String httpGetJson(String url, JSONObject paramJson) {
        Map<String, String> param = new HashMap<>();
        if (paramJson != null) {
            for (String key : paramJson.keySet()) {
                param.put(key, paramJson.get(key).toString());
            }
        }
        return httpGetWithHeader(url, param, new HashMap<>());
    }

    public static <T> String httpGetT(String url, T t) {
        // new BeanMap 会生成一个class属性
        Map<Object, Object> paramMap = new BeanMap(t);
        Map<String, String> param = new HashMap<>();
        for (Map.Entry<Object, Object> entry : paramMap.entrySet()) {
            if(entry.getKey()!=null&&entry.getValue()!=null&&!"class".equals(entry.getKey())){
                param.put(entry.getKey().toString(), entry.getValue().toString());
            }
        }
        paramMap.clear();
        return httpGetWithHeader(url, param, new HashMap<>());
    }


    public static HttpResponse doGet(String host, String path, String method,
                                     Map<String, String> headers,
                                     Map<String, String> querys)
            throws Exception {
        headers = enableGrayLevel(headers);
        HttpClient httpClient = wrapClient(host);
        HttpGet request = new HttpGet(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        return httpClient.execute(request);
    }

    private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(host);
        if (!StringUtils.isBlank(path)) {
            sbUrl.append(path);
        }
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }

        return sbUrl.toString();
    }

    private static CloseableHttpClient wrapClient(String host) {
        CloseableHttpClient httpClient = new DefaultHttpClient();
        if (host.startsWith("https://")) {
            sslClient(httpClient);
        }

        return httpClient;
    }

    private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] xcs, String str) {

                }
                public void checkServerTrusted(X509Certificate[] xcs, String str) {

                }
            };
            ctx.init(null, new TrustManager[] { tm }, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }

    private static Map<String, String> enableGrayLevel(Map<String, String> headerMap){
        if (headerMap == null) {
            headerMap = new HashMap<>();
        }
        if(grayLevel) {
            headerMap.put("uid", grayUid);
        }
        return headerMap;
    }

}
